/*
 *  Copyright 2012 Anton Van Zyl. http://code.google.com/p/java-swiss-knife/
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package com.knife.String;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.text.DecimalFormat;

import com.knife.Math.Math;
import com.knife.Math.exception.InvalidDataException;

/**
 * Utility of String conversion functions.
 * 
 * <br/>
 * <br/>
 * Please visit <a
 * href="http://code.google.com/p/java-swiss-knife/">Java-Swiss-Knife</a> and
 * comment, rate, contribute or raise a issue/enhancement for my library. <br/>
 * 
 * @author Anton Van Zyl
 */
public final class StringConverterUtil {

	/**
	 * Filter out all non-7-bit characters.
	 * 
	 * @param inString
	 * @return filtered string
	 */
	public static String filterNonAscii(final String inString) throws CharacterCodingException {
		// Create the encoder and decoder for the character encoding
		Charset charset = Charset.forName("US-ASCII");
		CharsetDecoder decoder = charset.newDecoder();
		CharsetEncoder encoder = charset.newEncoder();
		// This line is the key to removing "unmappable" characters.
		encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
		String result = inString;

		// Convert a string to bytes in a ByteBuffer
		ByteBuffer bbuf = encoder.encode(CharBuffer.wrap(inString));

		// Convert bytes in a ByteBuffer to a character ByteBuffer and then to a
		// string.
		CharBuffer cbuf = decoder.decode(bbuf);
		result = cbuf.toString();

		return result;
	}

	/**
	 * Format cellphone number to the MSISDN format. If the international code
	 * is not specified ZA's a user entered country Code will be used.
	 * 
	 * @param cellphoneNr
	 * @return MSISDN cellphone number format
	 */
	public static String cellphoneToMSISDNString(final String cellphoneNr, final String countryCode) {

		String msisdn = cellphoneNr.replaceAll(" ", "");

		if (msisdn.startsWith("+"))
			msisdn = msisdn.substring(1);
		else if (msisdn.startsWith("0")) {
			msisdn = countryCode + msisdn.substring(1);
		}
		return msisdn;
	}

	/**
	 * Convert string to an HTML complaint string.
	 * 
	 * @param input
	 *            to convert
	 * @return the converted string
	 */
	public static String stringToHTMLString(final String input) throws NullPointerException {

		if (input == null)
			throw new NullPointerException("stringToHTMLString: input string is NULL");

		StringBuilder sb = new StringBuilder();
		int len = input.length();
		char c;

		for (int i = 0; i < len; i++) {
			c = input.charAt(i);

			// HTML Special Chars
			if (c == '"')
				sb.append("&quot;");
			else if (c == '&')
				sb.append("&amp;");
			else if (c == '<')
				sb.append("&lt;");
			else if (c == '>')
				sb.append("&gt;");
			else {
				int ci = 0xffff & c;
				if (ci < 160)
					// nothing special only 7 Bit
					sb.append(c);
				else {
					// Not 7 Bit use the unicode system
					System.out.println("Remove unicode character [value=" + String.valueOf(ci) + "]");
				}
			}
		}
		return sb.toString();
	}

	private static final String[] tensNames = { "", " Ten", " Twenty", " Thirty", " Forty", " Fifty", " Sixty", " Seventy", " Eighty", " Ninety" };

	private static final String[] numNames = { "", " One", " Two", " Three", " Four", " Five", " Six", " Seven", " Eight", " Nine", " Ten", " Eleven", " Twelve", " Thirteen",
			" Fourteen", " Fifteen", " Sixteen", " Seventeen", " Eighteen", " Nineteen" };

	private static String convertLessThanOneThousand(int number, boolean addAndWord) {
		String soFar;

		if (number % 100 < 20) {
			soFar = numNames[number % 100];
			number /= 100;
		} else {
			soFar = numNames[number % 10];
			number /= 10;

			soFar = tensNames[number % 10] + soFar;
			number /= 10;
		}
		if (number == 0 && addAndWord)
			return "And" + soFar;
		else if (number == 0)
			return soFar;
		if (addAndWord)
			return numNames[number] + " Hundred And" + soFar;
		else
			return numNames[number] + " Hundred " + soFar;
	}

	/**
	 * This will convert a basic <code>long</code> to a English word. The
	 * algorithm allows a number range -999 999 999 999 to 999 999 999 999.
	 * 
	 * @param number
	 * @return A word for a number
	 * @throws InvalidDataException
	 */
	public static String convertNUmberToWords(long number) throws InvalidDataException {
		if (number == 0) {
			return "Zero";
		}

		StringBuilder builder = new StringBuilder();

		if (number < 0) {
			number = Math.invertNumber(number);
			builder.append("Minus");
		}

		String snumber = String.valueOf(number);

		// pad with "0"
		String mask = "000000000000";
		DecimalFormat df = new DecimalFormat(mask);
		snumber = df.format(number);

		// XXXnnnnnnnnn
		int billions = Integer.parseInt(snumber.substring(0, 3));
		// nnnXXXnnnnnn
		int millions = Integer.parseInt(snumber.substring(3, 6));
		// nnnnnnXXXnnn
		int hundredThousands = Integer.parseInt(snumber.substring(6, 9));
		// nnnnnnnnnXXX
		int thousands = Integer.parseInt(snumber.substring(9, 12));
		// nnnnnnnnnnXX
		int hundred = Integer.parseInt(snumber.substring(10, 12));

		String tradBillions;
		switch (billions) {
		case 0:
			tradBillions = "";
			break;
		case 1:
			tradBillions = convertLessThanOneThousand(billions, false) + " Billion ";
			break;
		default:
			tradBillions = convertLessThanOneThousand(billions, false) + " Billion ";
		}
		builder.append(tradBillions);

		String tradMillions;
		switch (millions) {
		case 0:
			tradMillions = "";
			break;
		case 1:
			tradMillions = convertLessThanOneThousand(millions, false) + " Million ";
			break;
		default:
			tradMillions = convertLessThanOneThousand(millions, false) + " Million ";
		}
		builder.append(tradMillions);

		String tradHundredThousands;
		switch (hundredThousands) {
		case 0:
			tradHundredThousands = "";
			break;
		case 1:
			tradHundredThousands = "One Thousand ";
			break;
		default:
			tradHundredThousands = convertLessThanOneThousand(hundredThousands, false) + " Thousand ";
		}
		builder.append(tradHundredThousands);

		String tradThousand;
		if (number > 100 && hundred > 0)
			tradThousand = convertLessThanOneThousand(thousands, true);
		else
			tradThousand = convertLessThanOneThousand(thousands, false);
		builder.append(tradThousand);

		// remove extra spaces!
		return builder.toString().replaceAll("^\\s+", "").replaceAll("\\b\\s{2,}\\b", " ");
	}

}
